#include "quiche/balsa/header_properties.h"

#include <string>

#include "absl/container/flat_hash_set.h"
#include "absl/strings/string_view.h"
#include "quiche/common/platform/api/quiche_test.h"

namespace quiche::header_properties::test {
namespace {

TEST(HeaderPropertiesTest, IsMultivaluedHeaderIsCaseInsensitive) {
  EXPECT_TRUE(IsMultivaluedHeader("content-encoding"));
  EXPECT_TRUE(IsMultivaluedHeader("Content-Encoding"));
  EXPECT_TRUE(IsMultivaluedHeader("set-cookie"));
  EXPECT_TRUE(IsMultivaluedHeader("sEt-cOOkie"));
  EXPECT_TRUE(IsMultivaluedHeader("X-Goo" /**/ "gle-Cache-Control"));
  EXPECT_TRUE(IsMultivaluedHeader("access-control-expose-HEADERS"));

  EXPECT_FALSE(IsMultivaluedHeader("set-cook"));
  EXPECT_FALSE(IsMultivaluedHeader("content-length"));
  EXPECT_FALSE(IsMultivaluedHeader("Content-Length"));
}

TEST(HeaderPropertiesTest, IsInvalidHeaderKeyChar) {
  EXPECT_TRUE(IsInvalidHeaderKeyChar(0x00));
  EXPECT_TRUE(IsInvalidHeaderKeyChar(0x06));
  EXPECT_TRUE(IsInvalidHeaderKeyChar(0x09));
  EXPECT_TRUE(IsInvalidHeaderKeyChar(0x1F));
  EXPECT_TRUE(IsInvalidHeaderKeyChar(0x7F));
  EXPECT_TRUE(IsInvalidHeaderKeyChar(' '));
  EXPECT_TRUE(IsInvalidHeaderKeyChar('"'));
  EXPECT_TRUE(IsInvalidHeaderKeyChar('\t'));
  EXPECT_TRUE(IsInvalidHeaderKeyChar('\r'));
  EXPECT_TRUE(IsInvalidHeaderKeyChar('\n'));
  EXPECT_TRUE(IsInvalidHeaderKeyChar('}'));

  EXPECT_FALSE(IsInvalidHeaderKeyChar('a'));
  EXPECT_FALSE(IsInvalidHeaderKeyChar('B'));
  EXPECT_FALSE(IsInvalidHeaderKeyChar('7'));
  EXPECT_FALSE(IsInvalidHeaderKeyChar(0x42));
  EXPECT_FALSE(IsInvalidHeaderKeyChar(0x7C));
  EXPECT_FALSE(IsInvalidHeaderKeyChar(0x7E));
}

TEST(HeaderPropertiesTest, IsInvalidHeaderKeyCharAllowDoubleQuote) {
  EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x00));
  EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x06));
  EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x09));
  EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x1F));
  EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x7F));
  EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote(' '));
  EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote('\t'));
  EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote('\r'));
  EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote('\n'));
  EXPECT_TRUE(IsInvalidHeaderKeyCharAllowDoubleQuote('}'));

  EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote('"'));
  EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote('a'));
  EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote('B'));
  EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote('7'));
  EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x42));
  EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x7C));
  EXPECT_FALSE(IsInvalidHeaderKeyCharAllowDoubleQuote(0x7E));
}

TEST(HeaderPropertiesTest, IsInvalidHeaderChar) {
  EXPECT_TRUE(IsInvalidHeaderChar(0x00));
  EXPECT_TRUE(IsInvalidHeaderChar(0x06));
  EXPECT_TRUE(IsInvalidHeaderChar(0x1F));
  EXPECT_TRUE(IsInvalidHeaderChar(0x7F));

  EXPECT_FALSE(IsInvalidHeaderChar(0x09));
  EXPECT_FALSE(IsInvalidHeaderChar(' '));
  EXPECT_FALSE(IsInvalidHeaderChar('\t'));
  EXPECT_FALSE(IsInvalidHeaderChar('\r'));
  EXPECT_FALSE(IsInvalidHeaderChar('\n'));
  EXPECT_FALSE(IsInvalidHeaderChar('a'));
  EXPECT_FALSE(IsInvalidHeaderChar('B'));
  EXPECT_FALSE(IsInvalidHeaderChar('7'));
  EXPECT_FALSE(IsInvalidHeaderChar(0x42));
  EXPECT_FALSE(IsInvalidHeaderChar(0x7D));
}

TEST(HeaderPropertiesTest, KeyMoreRestrictiveThanValue) {
  for (int c = 0; c < 255; ++c) {
    if (IsInvalidHeaderChar(c)) {
      EXPECT_TRUE(IsInvalidHeaderKeyChar(c)) << c;
    }
  }
}

TEST(HeaderPropertiesTest, HasInvalidHeaderChars) {
  const char with_null[] = "Here's l\x00king at you, kid";
  EXPECT_TRUE(HasInvalidHeaderChars(std::string(with_null, sizeof(with_null))));
  EXPECT_TRUE(HasInvalidHeaderChars("Why's \x06 afraid of \x07? \x07\x08\x09"));
  EXPECT_TRUE(HasInvalidHeaderChars("\x1Flower power"));
  EXPECT_TRUE(HasInvalidHeaderChars("\x7Flowers more powers"));

  EXPECT_FALSE(HasInvalidHeaderChars("Plenty of space"));
  EXPECT_FALSE(HasInvalidHeaderChars("Keeping \tabs"));
  EXPECT_FALSE(HasInvalidHeaderChars("Al\right"));
  EXPECT_FALSE(HasInvalidHeaderChars("\new day"));
  EXPECT_FALSE(HasInvalidHeaderChars("\x42 is a nice character"));
}

TEST(HeaderPropertiesTest, HasInvalidPathChar) {
  EXPECT_FALSE(HasInvalidPathChar(""));
  EXPECT_FALSE(HasInvalidPathChar("/"));
  EXPECT_FALSE(HasInvalidPathChar("invalid_path/but/valid/chars"));
  EXPECT_FALSE(HasInvalidPathChar("/path/with?query;fragment"));
  EXPECT_FALSE(HasInvalidPathChar("/path2.fun/my_site-root/!&$=,+*()/wow"));
  // Surprise! []{}^| are seen in requests on the internet.
  EXPECT_FALSE(HasInvalidPathChar("/square[brackets]surprisingly/allowed"));
  EXPECT_FALSE(HasInvalidPathChar("/curly{braces}surprisingly/allowed"));
  EXPECT_FALSE(HasInvalidPathChar("/caret^pipe|surprisingly/allowed"));
  // Surprise! Chrome sends backslash in query params, sometimes.
  EXPECT_FALSE(HasInvalidPathChar("/path/with?backslash\\hooray"));

  EXPECT_TRUE(HasInvalidPathChar("/path with spaces"));
  EXPECT_TRUE(HasInvalidPathChar("/path\rwith\tother\nwhitespace"));
  EXPECT_TRUE(HasInvalidPathChar("/backtick`"));
  EXPECT_TRUE(HasInvalidPathChar("/angle<brackets>also/bad"));
}

TEST(HeaderPropertiesTest, HasInvalidQueryChar) {
  EXPECT_FALSE(HasInvalidQueryChar(""));
  EXPECT_FALSE(HasInvalidQueryChar("/"));
  EXPECT_FALSE(HasInvalidQueryChar("valid_query/chars"));
  EXPECT_FALSE(HasInvalidQueryChar("query;fragment"));
  EXPECT_FALSE(HasInvalidQueryChar("query2.fun/my_site-root/!&$=,+*()/wow"));
  // Surprise! []{}^| are seen in requests on the internet.
  EXPECT_FALSE(HasInvalidQueryChar("square[brackets]surprisingly/allowed"));
  EXPECT_FALSE(HasInvalidQueryChar("curly{braces}surprisingly/allowed"));
  EXPECT_FALSE(HasInvalidQueryChar("caret^pipe|surprisingly/allowed"));
  // Surprise! Chrome sends backslash in query params, sometimes.
  EXPECT_FALSE(HasInvalidQueryChar("query_with?backslash\\hooray"));
  // Query params sometimes contain backtick or double quote.
  EXPECT_FALSE(HasInvalidQueryChar("backtick`"));
  EXPECT_FALSE(HasInvalidQueryChar("double\"quote"));

  EXPECT_TRUE(HasInvalidQueryChar("query with spaces"));
  EXPECT_TRUE(HasInvalidQueryChar("query\rwith\tother\nwhitespace"));
  EXPECT_TRUE(HasInvalidQueryChar("query_with_angle<brackets>also_bad"));
}

TEST(HeaderPropertiesTest, IsValidTokenVsHasInvalidHeaderChars) {
  absl::flat_hash_set<unsigned char> mismatch = {':'};
  for (int c = 0; c < 128; ++c) {
    if (mismatch.contains(c)) {
      continue;
    }

    unsigned char u_c = static_cast<unsigned char>(c);
    std::string s(1, u_c);
    EXPECT_EQ(IsValidToken(s), !IsInvalidHeaderKeyChar(u_c))
        << "char: [" << u_c << "], int = [" << c << "]";
  }
}

TEST(HeaderPropertiesTest, IsValidTokenEmptyAndMultiChar) {
  EXPECT_TRUE(IsValidToken("a"));
  EXPECT_TRUE(IsValidToken("GET"));
  EXPECT_TRUE(IsValidToken("GET'"));
  EXPECT_TRUE(IsValidToken("a-b-c"));
  EXPECT_TRUE(IsValidToken("!#$%&'*+-.^_`|~"));
  EXPECT_TRUE(IsValidToken("abcefghijklmnopqrstuvwxyz0123456789"));
  EXPECT_TRUE(
      IsValidToken("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                   "abcdefghijklmnopqrstuvwxyz"
                   "0123456789"
                   "!#$%&'*+-.^_`|~"));

  EXPECT_FALSE(IsValidToken("G ET"));
  EXPECT_FALSE(IsValidToken("G,ET"));
  EXPECT_FALSE(IsValidToken("G\tET"));
  EXPECT_FALSE(IsValidToken(absl::string_view("G\0ET", 3)));
  EXPECT_FALSE(IsValidToken("GET\""));
  EXPECT_FALSE(IsValidToken("GET\x85"));
  EXPECT_FALSE(IsValidToken("GET("));
  EXPECT_FALSE(IsValidToken("GET)"));
  EXPECT_FALSE(IsValidToken("GET{"));
  EXPECT_FALSE(IsValidToken("GET}"));
  EXPECT_FALSE(IsValidToken("GET}"));
  EXPECT_FALSE(IsValidToken("GET@"));
  EXPECT_FALSE(IsValidToken("GET["));
  EXPECT_FALSE(IsValidToken("GET\\"));
  EXPECT_FALSE(IsValidToken("GET]"));
  EXPECT_FALSE(IsValidToken("GET:"));
  EXPECT_FALSE(IsValidToken("GET;"));
  EXPECT_FALSE(IsValidToken("GET?"));
  EXPECT_FALSE(IsValidToken("GET="));
  EXPECT_FALSE(IsValidToken("GET/"));
  EXPECT_FALSE(IsValidToken("GET\""));
  EXPECT_FALSE(IsValidToken("GET<"));
  EXPECT_FALSE(IsValidToken("GET>"));
  EXPECT_FALSE(IsValidToken("GET,"));
  EXPECT_FALSE(IsValidToken("GET\x7F"));
  EXPECT_FALSE(IsValidToken(""));
}

}  // namespace
}  // namespace quiche::header_properties::test
